package com.ideaaedi.commonds.time;

import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.StringUtils;

import java.time.*;
import java.time.format.DateTimeFormatter;

/**
 * 带时区的时间工具类
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2100.10.15.LTS17
 */
@UtilityClass
public class ZonedDateTimeUtil {

    /**
     * 获取当前瞬间，指定时区的时间
     * <pre>
     * 假设：当前系统时间、时区分别为：2025-06-10 16:27:11 Asia/Shanghai
     * 那么：此时ZonedDateTime.now(ZoneId.of("UTC")) 值为 2025-06-10 08:27:11 UTC
     * </pre>
     *
     * @param zone 时区
     *
     * @return 目标时区的当前时间
     */
    public static ZonedDateTime now(ZoneId zone) {
        return ZonedDateTime.now(zone);
    }

    /**
     * 忽略时区，直接获取纯时间部分
     * <pre>
     * 假设：zonedDateTime 值为 2025-06-10 08:27:11 UTC
     * 那么：无论zonedDateTime的时区与本地时区是否一致，此时 zonedDateTime.toLocalDateTime() 值都为 2025-06-10 08:27:11
     * </pre>
     *
     * @param zonedDateTime 带有时区信息的时间
     * @return 纯时间部分
     */
    public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime) {
        return zonedDateTime.toLocalDateTime();
    }

    /**
     * 忽略时区，直接获取纯时间部分
     * <pre>
     * 假设：zonedDateTime 值为 2025-06-11 20:27:11 UTC
     * 那么：无论zonedDateTime的时区与本地时区是否一致，此时 zonedDateTime.toLocalDate() 值都为 2025-06-11
     * </pre>
     *
     * @param zonedDateTime 带有时区信息的时间
     * @return 纯时间部分
     */
    public static LocalDate toLocalDate(ZonedDateTime zonedDateTime) {
        return zonedDateTime.toLocalDate();
    }

    /**
     * 忽略时区，直接获取纯时间部分
     * <pre>
     * 假设：zonedDateTime 值为 2025-06-11 20:27:11 UTC
     * 那么：无论zonedDateTime的时区与本地时区是否一致，此时 zonedDateTime.toLocalTime() 值都为 20:27:11
     * </pre>
     *
     * @param zonedDateTime 带有时区信息的时间
     * @return 纯时间部分
     */
    public static LocalTime toLocalTime(ZonedDateTime zonedDateTime) {
        return zonedDateTime.toLocalTime();
    }

    /**
     * 给定时间、时区，构建ZonedDateTime
     * <pre>
     * 假设：LocalDateTime systemTime  值为 2025-06-10 16:27:11
     *      ZoneId utc = ZoneId.of("UTC")
     * 那么：无论utc与本地时区是否一致，此时systemTime.atZone(utc) 值都为 2025-06-10 16:27:11 UTC
     * </pre>
     *
     * @param localDateTime 时间（本身不包含时区）
     * @param zoneId        时区
     * @return 带有时区信息的 ZonedDateTime
     */
    public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zoneId) {
        return localDateTime.atZone(zoneId);
    }

    /**
     * 给定日期、时区，构建ZonedDateTime
     * <pre>
     * 假设：LocalDate systemTime  值为 2025-06-10
     *      ZoneId utc = ZoneId.of("UTC")
     * 那么：无论utc与本地时区是否一致，此时systemTime.atZone(utc) 值都为 2025-06-10 00:00:00 UTC
     * </pre>
     *
     * @param localDate 日期（本身不包含时区）
     * @param zoneId    时区
     * @return 时分秒为00:00:00的ZonedDateTime
     */
    public static ZonedDateTime toZonedDateTime(LocalDate localDate, ZoneId zoneId) {
        return localDate.atStartOfDay(zoneId);
    }

    /**
     * 时间偏移量不变，将ZonedDateTime转换为指定时区下指定格式的时间字符串
     *
     * @param zonedDateTime 需要格式化的 ZonedDateTime
     * @param formatter     要转化为的目标时区及目标格式
     *        <pre>{@code DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(zoneId);}</pre>
     *      注：如果创建DateTimeFormatter时不指定时区，那么将默认使用本地时区
     * @return 对应时区下该格式的字符串（转换前后时区+时间代表的时间偏移量不变）
     */
    public static String formatZonedDateTime(ZonedDateTime zonedDateTime, DateTimeFormatter formatter) {
        return zonedDateTime.format(formatter);
    }

    /**
     * 将字符串转换为时间值，并指定该时间所属时区
     * <pre>
     * 假设：dateTimeString 为 "2025-06-11 20:27:11"
     *      formatter 为 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("UTC"))
     * 那么：无论formatter的时区与本地时区是否一致，此时 ZonedDateTime.parse(dateTimeString, formatter) 值都为 2025-06-11 20:27:11 UTC
     * </pre>
     *
     * @param dateTimeString 字符串表示的时间
     * @param formatter      dateTimeString的格式及该时间所在的时区
     *        <pre>{@code DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(zoneId);}</pre>
     *      注：如果创建DateTimeFormatter时不指定时区，那么将默认使用本地时区
     * @return 解析后的ZonedDateTime
     */
    public static ZonedDateTime parseZonedDateTime(String dateTimeString, DateTimeFormatter formatter) {
        return ZonedDateTime.parse(dateTimeString, formatter);
    }

    /**
     * test
     */
    public static void main(String[] args) {
        ZoneId systemZoneId = ZoneId.systemDefault();
        LocalDateTime systemTime = LocalDateTime.now();

        ZoneId utc = ZoneId.of("UTC");
        ZonedDateTime zdt = ZonedDateTime.now(utc);
        StringBuilder sb = new StringBuilder();
        sb.append(StringUtils.rightPad("本地时区", 102, '-')).append(systemZoneId.getId()).append(System.lineSeparator());
        sb.append(StringUtils.rightPad("本地时间", 102, '-')).append(systemTime).append(System.lineSeparator());
        sb.append(StringUtils.rightPad("ZonedDateTime.now(ZoneId)", 105, '-')).append(zdt).append(System.lineSeparator());
        sb.append(StringUtils.rightPad("ZonedDateTime#toLocalDateTime()", 105, '-'))
                .append("toLocalDateTime前：").append(zdt)
                .append("\t\ttoLocalDateTime后：").append(zdt.toLocalDateTime()).append(System.lineSeparator());
        sb.append(StringUtils.rightPad("ZonedDateTime#toLocalDate()", 105, '-'))
                .append("toLocalDate前：").append(zdt)
                .append("\t\ttoLocalDate后：").append(zdt.toLocalDate()).append(System.lineSeparator());
        sb.append(StringUtils.rightPad("ZonedDateTime#toLocalTime()", 105, '-'))
                .append("toLocalTime前：").append(zdt)
                .append(		"toLocalTime后：").append(zdt.toLocalTime()).append(System.lineSeparator());
        sb.append(StringUtils.rightPad("LocalDateTime#atZone(ZoneId)", 105, '-'))
                .append("atZone前：").append(String.format("时间%s, 时区UTC", systemTime))
                .append("\t\tatZone后：").append(systemTime.atZone(utc)).append(System.lineSeparator());
        LocalDate localDate = systemTime.toLocalDate();
        sb.append(StringUtils.rightPad("LocalDate#atStartOfDay(ZoneId)", 105, '-'))
                .append(String.format("atStartOfDay前：日期%s, 时区%s", localDate, utc))
                .append("\t\tatStartOfDay后：").append(localDate.atStartOfDay(utc)).append(System.lineSeparator());
        sb.append(StringUtils.rightPad("ZonedDateTime#format(DateTimeFormatter-withZone)", 105, '-'))
                .append("ofPattern前：").append(zdt)
                .append("\t\tofPattern到Asia/Shanghai时区后：").append(
                        zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("Asia/Shanghai")))
                )
                .append(System.lineSeparator());
        String aDateTime = "2025-06-10 12:20:15";
        sb.append(StringUtils.rightPad("ZonedDateTime#parse(dateTimeString, DateTimeFormatter-withZone)", 105, '-'))
                .append("parse前：").append(String.format("时间%s、时区UTC", aDateTime))
                .append("\t\tparse后：").append(
                        ZonedDateTime.parse(aDateTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("UTC")))
                )
                .append(System.lineSeparator());
        sb.append(StringUtils.rightPad("ZonedDateTime#withZoneSameLocal(ZoneId)：只修改时区，时间不动（即：不作时间转换）", 89, '-'))
                .append("修改前：").append(zdt)
                .append("\t\t修改后：").append(zdt.withZoneSameLocal(ZoneId.of("Asia/Shanghai"))).append(System.lineSeparator());
        sb.append(StringUtils.rightPad("ZonedDateTime#withZoneSameInstant(ZoneId)：修改时区，时间对应变化（即：作时间转换，转换前后是同一瞬时）", 82, '-'))
                .append("修改前：").append(zdt)
                .append("\t\t修改后：").append(zdt.withZoneSameInstant(ZoneId.of("Asia/Shanghai"))).append(System.lineSeparator());
        System.err.println(sb);
    }
}